package commons.params;

import commons.io.in.Data_in;
import commons.params.ihm.A_DlgParams;
import commons.params.param_component.IParamComponent;
import commons.params.param_component.ParamComponent_simple;
import commons.utils.Properties_INI;
import commons.utils.UtilsFile;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;


public abstract class A_ParamsManager {

	private HashMap<String, IParamComponent> mapParamsComponent;

	//**************************************************************************

	public A_DlgParams dlg;
	protected Data_in dataIn; // contient l'ensemble des couches lues dans le répertoire d'entrée de données brutes

	private String paramsName;
	public void setName(String name) {this.paramsName = name;}
	public String getName() {return paramsName;}
	public String getName_NoNameIfNull() {if (isDefaultParams()) return "<Sans Nom>"; else return paramsName;}
	public boolean isDefaultParams() {return paramsName==null || paramsName.length()==0;}

	private boolean bEditUnsaved;
	public boolean hasEditUnsaved(){return bEditUnsaved;}

	abstract public String getDirectoryUserParams();

	// La dialog de param varie selon les derivées
	abstract protected void createDlg(JFrame frameParent);

	// Création des ParamComponent spécifique à la derivee
	abstract protected void initParamComponent();

	// En sortie de la dlg, on crée un objet Params qui reprend l'ensemble des paramètres
	//  en type de base + classes spécifiques
	abstract protected A_Params createParams();

	//**********************************************************************
	// APRES RECUP DE TOUTES LES VALEURS DE LA DLG
	// AVANT LES CONTROLES et REECRITURE DANS LA DLG
	// Re-création des valeurs possibles pour les comboBox, list et autres tables
	// En effet, elles peuvent dépendre des différents autres paramètres saisis par l'utilisateur
	// Ces reinit de valeurs possibles sont à réaliser AVANT appel à
	//  checkValid() et writeToComponent() et donc avant controlAllParams()
	//**********************************************************************
	abstract protected void setValeursPossible();

	//**************************************************************************
	// En argument, le Data_in contenant les données d'entrées
	public A_ParamsManager(JFrame frameParent, Data_in dataIn) {
		this.dataIn = dataIn;
		createDlg(frameParent);
		this.mapParamsComponent = new HashMap<String, IParamComponent>();
		initParamComponent();
	}

	
	static private String lastParamsNameUsed = null;
	
	public A_Params ouvreDialogParametrage() {
		dlg.initialize(dataIn, this, lastParamsNameUsed);
		dlg.setVisible(true);
		if (dlg.hasUserCanceled()) {
			dlg.dispose();
			dlg = null;
			return null;
		} else {
			A_Params params = createParams();
			lastParamsNameUsed = params.getParamName();
			dlg.dispose();
			dlg = null;		
			return params;
		}
	}


	protected void registerParamComponent (IParamComponent paramComponent) {
		assert (!mapParamsComponent.containsKey(paramComponent.getName())) : "Param en doublon : " + paramComponent.getName();
		assert (!hasThisJComponentAlreadyBeenRegistered(paramComponent.getComponent())) : "JComponent déjà utilisé pour le param : " + paramComponent.getName();

		mapParamsComponent.put(paramComponent.getName(), paramComponent);
	}
	protected IParamComponent getParamComponent(String name) {
		assert (mapParamsComponent.containsKey(name)) : "Param non registered : " + name;
		return mapParamsComponent.get(name);
	}
	protected ParamComponent_simple getParamComponent_s(String name) {
		return (ParamComponent_simple)getParamComponent(name);
	}

	// pour de la verif de code (type assert), mais ca doit toujours renvoyer false en temps normal ...
	private boolean hasThisJComponentAlreadyBeenRegistered(JComponent component) {
		for (IParamComponent pc : mapParamsComponent.values())
			if (pc.getComponent()==component)
				return true;
		return false;
	}

	// Lecture d'un fichier de paramétrage (param par défaut si paramsName="")
	// Puis affichage des valeurs lues dans la dlg, après un contrôle de cohérence
	// Méthode appelée par la dlg
	// On doit être en mode STOP HANDLE EVENTS !!
	public void loadParams(String paramsName){
		assert(dlg.IS_STOPPING_EVENTS());
		this.paramsName = paramsName;

		if (paramsName.length()==0)
			// Paramètres par défaut
			setDefaultParams();

		boolean bResLoad = loadFromFile();
		if (!bResLoad) {
			// Erreur au chargement, on relance l'init avec des params vierges
			loadParams("");
			return;
		}

		// Réinit des valeurs possibles
		setValeursPossible();
		// Controles de tous les paramètres
		controlAllParams();
		// Re-écriture dans la Dialog
		writeAllParamsToDlg();

		dlg.setTitle("Edition des paramètres : " + getName_NoNameIfNull());

		bEditUnsaved = false;
	}

	private void setDefaultParams() {
		for (IParamComponent paramComponent : mapParamsComponent.values())
			paramComponent.reinit();
	}

	//**************************************************************************
	//**************************************************************************

	// Appelé lors d'une modif utilisateur par tout component associé à un ParamComponent
	public void traiteOneModifUtilisateur() {
		if (dlg.IS_STOPPING_EVENTS())
			return;

		bEditUnsaved = true;

		// On stoppe la gestion des évenements liés aux modification dans les composants de la dlg
		dlg.STOP_HANDLE_EVENTS();

		// Lecture de toutes les données au Params
		readAllParamsFromDlg();
		// Réinit des valeurs possibles : certaines dépendent des paramètres modifiés par l'utilisateur
		setValeursPossible();
		// Controles de tous les paramètres
		controlAllParams();
		// Re-écriture dans la Dialog
		writeAllParamsToDlg();

		// On repermet la gestion des évenements liés aux modification dans les composant de la dlg
		dlg.START_HANDLE_EVENTS();
	}


	public void readAllParamsFromDlg() {
		for (IParamComponent paramComponent : mapParamsComponent.values())
			paramComponent.readFromComponent();
	}

	// Le controle gere entres autres le passage en bordure rouge pour les component
	//  dont les valeurs sont invalides
	protected void controlAllParams() {
		// Avant les contrôles :
		//  - validation par défaut des Tab et du bouton Go de la Dlg
		//  - validation par défaut des component
		// Les invalidations se feront au cas par cas lors des controles qui suivent
		dlg.validateAllTabs();
		dlg.validateGoButton();
		for (IParamComponent paramComponent : mapParamsComponent.values())
			paramComponent.validateComponent();

		// Contrôles standard pour tous les ParamComponent :
		//  nullité, format, respect des valeurs possibles permises etc.
		for (IParamComponent paramComponent_ : mapParamsComponent.values())
			paramComponent_.makeStandardControls();

		controlesSpeciaux();
	}

	protected void controlesSpeciaux() {}


	// Ecrit les valeurs des paramètres dans le component correspondant
	private void writeAllParamsToDlg () {
		// Les controles ont été effectués (avec invalidation eventuelle des component)
		// On demande simplement à chaque ParamComponent d'ecrire sa valeur
		//  dans le component de la Dlg auquel il est associé
		for (IParamComponent paramComponent_ : mapParamsComponent.values())
			paramComponent_.writeToComponent();

		// On alerte la dlg que tout est écrit, si besoin de faire des post-traitements éventuels
		dlg.allParamsWritten();
	}

	//**************************************************************************
	//**************************************************************************

	private boolean loadFromFile() {
		if (paramsName == null || paramsName.length()==0)
			return true; // Rien à charger

		String filePath = getParamPath();
		Properties_INI props = new Properties_INI();
		try {
			InputStream is = new FileInputStream(filePath);
			props.load(is);
			is.close();
			loadParamsFromProperties(props);
		} catch (FileNotFoundException ex) {
			JOptionPane.showMessageDialog(dlg, "Fichier non trouvé : "+ paramsName , "Erreur au chargement du fichier", JOptionPane.ERROR_MESSAGE);
			return false;
		} catch (IOException ex) {
			JOptionPane.showMessageDialog(dlg, "Erreur au chargement du fichier : " + paramsName , "Erreur au chargement du fichier", JOptionPane.ERROR_MESSAGE);
			return false;
		} catch (NumberFormatException ex) {
			JOptionPane.showMessageDialog(dlg, "Erreur au chargement du fichier : " + paramsName + "\n Le fichier est corrompu et comporte des valeurs devant être des entiers mais impossibles à convertir en entier.", "Erreur au chargement du fichier", JOptionPane.ERROR_MESSAGE);
			return false;
		} catch (Exception ex) {
			JOptionPane.showMessageDialog(dlg, "Erreur au chargement du fichier : " + paramsName + "\n msg erreur = " + ex.getMessage(), "Erreur au chargement du fichier", JOptionPane.ERROR_MESSAGE);
			return false;
		}
		return true;
	}

	// Renvoie false si la demande de sauvegarde a été annulée
	public boolean saveToFile() {
		if (isDefaultParams())
			return saveToFileAs();

		saveToFile (getParamPath());
		return true;
	}

	// Renvoie false si la demande de sauvegarde a été annulée
	public boolean saveToFileAs () {
		String newName = askForParamsName(paramsName);
		if (newName == null)
			// annulation de la sauvegarde
			return false;

		paramsName = newName;
		saveToFile (getParamPath());
		dlg.setTitle("Edition des paramètres : " + getName_NoNameIfNull());
		return true;
	}


	private void saveToFile (String path) {
		try {
			OutputStream os = new FileOutputStream(path);
			writeParamsToProperties().store(os, "ATTENTION : ce fichier de parametrage a ete genere par l'outil et ne doit pas etre modifie manuellement");
			writeParamsToProperties().saveToFile(path);
			os.close();
			bEditUnsaved = false;
		} catch (FileNotFoundException ex) {
			JOptionPane.showMessageDialog(dlg, "Fichier non trouvé : " + paramsName , "Erreur à l'enregistrement du fichier", JOptionPane.ERROR_MESSAGE);
		} catch (IOException ex) {
			JOptionPane.showMessageDialog(dlg, "Erreur sur le fichier : "+ paramsName , "Erreur à l'enregistrement du fichier", JOptionPane.ERROR_MESSAGE);
		}
	}


	// Stockage de tous les paramètres dans un object props, en vue de la sauvegade dans un fichier
	private Properties_INI writeParamsToProperties() {
		Properties_INI props = new Properties_INI();
		for (IParamComponent paramComponent : mapParamsComponent.values())
			paramComponent.storeInProperties(props);

		return props;
	}


	// Récupération de tous les paramètres du props (issus de la lecture du fichier de paramètre)
	private void loadParamsFromProperties(Properties_INI props) {

		String listPropertiesEnErreur = "";

		for (IParamComponent paramComponent : mapParamsComponent.values()) {
			if (!paramComponent.loadFromProperties(props))
				listPropertiesEnErreur += " - " + paramComponent.getName() + "\n";
		}

		if (!listPropertiesEnErreur.isEmpty())
			JOptionPane.showMessageDialog(null, "Les paramètres suivants sont introuvables ou erronés dans le fichier de paramètres :\n\n" + listPropertiesEnErreur, "Erreur dans le fichier de paramètres", JOptionPane.ERROR_MESSAGE);
	}
	
	//**************************************************************************
	//**************************************************************************

	private String getParamPath() {
		if (isDefaultParams())
			return null;
		else
			return getParamPath(paramsName);
	}
	private String getParamPath(String paramsName_) {
		return getDirectoryUserParams() + "\\" + paramsName_ + ".ini";
	}

	private String askForParamsName(String defaultName) {
		// Choix d'un nom pour le fichier de sauvegarde
		String name="";
		while (name.trim().length()==0) {
			name = JOptionPane.showInputDialog(dlg, "Veuillez saisir le nom du fichier de paramètres", defaultName/*"Sauvegarde des paramètres"*/ /*, JOptionPane.PLAIN_MESSAGE*/);
			if (name==null || name.length()==0)
				return null;
		}

		// Le nom est-il valide ? (cf. le fichier de paramètre aura le nom choisi par l'utilisateur)
		if (!UtilsFile.isFileNameValide(name)) {
			JOptionPane.showMessageDialog(dlg, "Le nom entré contient des caractères invalides (< > / \\ \" * : |  etc.)", "Nom de fichier de paramètres incorrect", JOptionPane.ERROR_MESSAGE);
			// on recommance !
			return askForParamsName(name);
		}

		// Le nom du fichier existe-t-il déjà ?
		if (UtilsFile.fileExists(getParamPath(name))) {
			// On demande confirmation
			int reponse = JOptionPane.showConfirmDialog(
					dlg,
					"Le nom choisi existe déjà.\nVoulez-vous remplacer le fichier déjà existant ?" ,
					"Sauvegarde dans un fichier déjà existant : " + name + " ?",
					JOptionPane.YES_NO_CANCEL_OPTION,
					JOptionPane.QUESTION_MESSAGE);
			if (reponse == 0)
				// Demande d'écrasement
				return name;
			else if (reponse == 1)
				// Pas d'écrasement du fichier existant : on redemande un nouveau nom (récursivement)
				return askForParamsName(name);
			else
				// Annulation de la sauvegarde
				return null;

		} else {
			return name;
		}
	}

}
